package com.liuyjy.es.utils;

import com.alibaba.fastjson2.JSON;
import com.liuyjy.core.entity.PageResult;
import com.liuyjy.core.exception.EsException;
import com.liuyjy.es.annotation.EsDocument;
import com.liuyjy.es.annotation.EsField;
import com.liuyjy.es.annotation.EsId;
import com.liuyjy.utils.utils.ObjConvertUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;
import org.elasticsearch.xcontent.XContentType;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author liuyjy
 * @Date 2025/2/24 17:36
 * @Description: TODO
 **/
@Slf4j
public class EsEntityUtils {


    // 生成索引Mapping
    public static Map<String, Object> buildMapping(Class<?> clazz) {
        Map<String, Object> properties = new HashMap<>();
        for (Field field : clazz.getDeclaredFields()) {
            EsField esField = field.getAnnotation(EsField.class);
            if (esField != null) {
                Map<String, Object> fieldProperties = new HashMap<>();
                fieldProperties.put("type", esField.type());
                fieldProperties.put("analyzer", esField.analyzer());
                fieldProperties.put("format", esField.format());
                properties.put(field.getName(), fieldProperties);
            }
        }
        Map<String, Object> result = new HashMap<>();
        result.put("properties", properties);
        return result;
    }


    public static Map<String, Object> buildPinyinAnalyzer() {
        Map<String, Object> settings = new HashMap<>();

        // 拼音分词器配置
        Map<String, Object> analysis = new HashMap<>();
        Map<String, Object> analyzer = new HashMap<>();

        Map<String, Object> pinyinAnalyzer = new HashMap<>();
        pinyinAnalyzer.put("type", "custom");
        pinyinAnalyzer.put("tokenizer", "my_pinyin");

        analyzer.put("pinyin_analyzer", pinyinAnalyzer);
        analysis.put("analyzer", analyzer);

        Map<String, Object> tokenizer = new HashMap<>();
        Map<String, Object> pinyinTokenizer = new HashMap<>();
        pinyinTokenizer.put("type", "pinyin");
        pinyinTokenizer.put("keep_first_letter", true);
        pinyinTokenizer.put("keep_separate_first_letter", false);
        pinyinTokenizer.put("keep_full_pinyin", true);
        pinyinTokenizer.put("keep_original", true);
        pinyinTokenizer.put("limit_first_letter_length", 16);
        pinyinTokenizer.put("lowercase", true);

        tokenizer.put("my_pinyin", pinyinTokenizer);
        analysis.put("tokenizer", tokenizer);

        settings.put("analysis", analysis);
        return settings;
    }

    public static Map<String, Object> buildDefaultMapping() {
        Map<String, Object> mapping = new HashMap<>();
        Map<String, Object> properties = new HashMap<>();

        // 默认字段映射
        properties.put("id", new HashMap<String, Object>() {{
            put("type", "keyword");
        }});
        properties.put("createTime", new HashMap<String, Object>() {{
            put("type", "date");
        }});
        properties.put("updateTime", new HashMap<String, Object>() {{
            put("type", "date");
        }});

        mapping.put("properties", properties);
        return mapping;
    }

    /**
     * 将ES搜索结果转换为目标对象
     *
     * @param hit   搜索结果
     * @param clazz 目标类型
     * @return 转换后的对象
     */
    public static <T> T convertSearchHit(SearchHit hit, Class<T> clazz) {
        try {
            String source = hit.getSourceAsString();
            return JSON.parseObject(source, clazz);
        } catch (Exception e) {
            throw new EsException("Convert search hit error", e);
        }
    }

    /**
     * 构建分页结果
     *
     * @param response 搜索响应
     * @param clazz    目标类型
     * @return 分页结果
     */
    public static <T> PageResult<T> buildPageResult(SearchResponse response, Class<T> clazz) {
        PageResult<T> result = new PageResult<>();
        result.setTotal(response.getHits().getTotalHits().value);
        result.setPage(Long.valueOf(response.getHits().getHits().length));
        result.setLimit(Long.valueOf(response.getHits().getHits().length));
        List<T> data = Arrays.stream(response.getHits().getHits())
                .map(hit -> convertSearchHit(hit, clazz))
                .collect(Collectors.toList());
        result.setRows(data);

        return result;
    }


    // ================== 工具方法 ==================
    public static String getIndexName(Class<?> clazz) {
        EsDocument annotation = clazz.getAnnotation(EsDocument.class);
        if (annotation == null) {
            //throw new EsException("实体类缺少@EsDocument注解");
            return "";
        }
        return annotation.indexName();
    }

    // 自动提取实体类ID值
    public static <T> String getEntityId(T entity) {
        try {
            Field idField = Arrays.stream(entity.getClass().getDeclaredFields())
                    .filter(f -> f.isAnnotationPresent(EsId.class))
                    .findFirst()
                    .orElseThrow(() -> new EsException("实体类缺少@EsId注解"));

            idField.setAccessible(true);
            return idField.get(entity).toString();
        } catch (IllegalAccessException e) {
            throw new EsException("获取ID失败", e);
        }
    }


    private <T> List<T> parseHits(SearchHits hits, Class<T> clazz) {
        return Arrays.stream(hits.getHits())
                .map(hit -> parseHit(hit, clazz))
                .collect(Collectors.toList());
    }

    private <T> T parseHit(SearchHit hit, Class<T> clazz) {
        return ObjConvertUtil.convert(hit.getSourceAsString(), clazz);
    }

    /**
     * 根据给定的类生成Elasticsearch映射的XContentBuilder对象
     * 此方法解析类中的字段，特别是带有EsField注解的字段，以生成映射
     *
     * @param clazz 要解析映射的类，不能为空
     * @param <T>   泛型参数，表示传入类的类型
     * @return XContentBuilder对象，用于构建映射JSON
     * @throws IllegalArgumentException 如果传入的类为null
     */
    public <T> XContentBuilder parseMapping(Class<T> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("Class cannot be null");
        }

        try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
            builder.startObject();
            {
                builder.startObject("properties");
                for (Field field : clazz.getDeclaredFields()) {
                    EsField esField = field.getAnnotation(EsField.class);
                    if (esField == null) {
                        continue;
                    }
                    builder.startObject(field.getName());
                    {
                        builder.field("type", esField.type().getEsType());

                        String analyzer = esField.analyzer();
                        String format = esField.format();
                        if (StringUtils.isNotBlank(analyzer)) {
                            builder.field("analyzer", analyzer);
                        }
                        if (StringUtils.isNotBlank(format)) {
                            builder.field("format", format);
                        }
                    }
                    builder.endObject();
                }
                builder.endObject();
            }
            builder.endObject();
            return builder;
        } catch (IOException e) {
            log.error("Failed to create XContentBuilder", e);
            throw new EsException("Failed to create XContentBuilder", e);
        }
    }
}
